/**
 *  amoobject - C++ simple, safe and fast signal/slot mechanism implementation.
 *
 *  Copyright (C) 2011-2012 Abdeslam MOKRANI (abdesmok@gmail.com).
 *  All rights reserved.
 *
 *  This file is part of amo project.
 *
 *  This file may be freely redistributed and/or modified under ther terms
 *  of the GNU Lesser General Public (LGPL) License as published by the
 *  Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This file is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this file. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "../amoobject.h"
#include "test.h"

using namespace amo;

class Object1 : public Object {
public:
   void traceEmitChanged(int i) {
      TRACE(this << "Object1 emit_changed" << i);
   }
   
   void emitChanged(int i) {
      traceEmitChanged(i);
      emit_changed(i);
   }
   
   AMO_SIGNAL(clicked)
   AMO_SIGNAL(clicked2)
   AMO_SIGNAL(changed, int)
   AMO_SIGNAL(changed2, int, int, char, const char *)
};

class Object2 : public Object {
public:
   void click() {
      TRACE(this << "Object2 clicked");
   }
   
   virtual void click2() {
      TRACE(this << "Object2 clicked 2");
   }
   
   bool changed(int i) {
      TRACE(this << "Object2 changed int" << i);
      return true;
   }
   
   void traceThrowInt(int i) {
      TRACE(this << "Object2 throws int" << i);
   }
   
   void throwInt(int i) {
      traceThrowInt(i);
      throw(i);
   }
   
   void changed(char c) {
      TRACE(this << "changed char" << c);
   }
   
   void changed(int i, int i2, char c, const char *text) {
      TRACE(this << "Object2 changed int int char char *" << i << i2 << c << text);
   }
   
   AMO_SIGNAL(action);
   AMO_SIGNAL(charRead, char);
};

class Object3 : public Object {
public:
   void click() {
      TRACE(this << "Object3 clicked");
   }
   
   void action() {
      TRACE(this << "Object3 action");
   }
   
   void changed(int i) {
      TRACE(this << "Object2 changed int" << i);
   }
};

class Object4 : public Object {
public:
   ~Object4() {
      traceDeleted();
   }
   void traceDeleted() {
      TRACE(this << "Object4 remove");
   }
   void printMe() {
      TRACE(this << "Object4 print me");
   }
   void deleteMe() {
      delete this;
   }
   
   AMO_SIGNAL(remove)
};

class Object5 : public Object {
public:
   Object *object;
   Object5(Object *object = 0) : object(object) {}
   
   void traceDeleteObject(Object *object) {
      TRACE(this << "Object5 delete object" << object);
   }
   
   void deleteObject() {
      traceDeleteObject(object);
      if (object) delete object;
      object = 0;
   }
};

class Base1 {
public:
   int i;
};

class Base2 {
public:
   virtual ~Base2() {}
   int i;
};

class Object6 : public Base1, public virtual Base2, public Object {
public:
   void tracedisconnectObject1clicked(Object *object) {
      TRACE(this << "Object6 disconnect Object1 clicked" << object);
   }
   
   void disconnectO1Click() {
      if (dynamic_cast<Object1 *>(this->sender())) {
         static_cast<Object1 *>(this->sender())->clicked().disconnect();
         tracedisconnectObject1clicked(this->sender());
      }
   }
   
   void traceConnectObject1clicked(Object *object) {
      TRACE(this << "Object6 connect Object1 clicked" << object << "to Object6::connected");
   }
   
   void connectObject1clicked() {
      if (dynamic_cast<Object1 *>(this->sender())) {
         static_cast<Object1 *>(this->sender())->clicked().connect(
            this,
            &Object6::connected
         );
         traceConnectObject1clicked(this->sender());
      }
   }
   
   void connected() {
      TRACE(this << "Object6 connected");
   }
};

class Object7 : public virtual Base2, public Object6 {
public:
   Object1 *o1;
   Object7(Object1 *o1) : o1(o1) {}
   
   void traceConnectObject1AndEmit456() {
      TRACE(this << "Object7 traceConnectObject1AndEmit456");
   }
   
   void connectObject1AndEmit456() {
      traceConnectObject1AndEmit456();
      changedSignal().connect(o1, &Object1::emitChanged);
      changedSignal().emit(this, 456);
   }
   
   void changed(int i) {
      TRACE(this << "Object7 changed" << i);
   }
   
   AMO_SIGNAL(changedSignal, int)
};

class Notifier : public Object
{
public:
   static void traceNotifierDestructed() {
      TRACE("Notifier destructed, signal emited")
   }
   
   ~Notifier() {
      traceNotifierDestructed();
		Notiy().emit(this);
   }
   
   AMO_SIGNAL(Notiy);
};

class Listener : public Object
{
public:
   static void traceConsructed() {
      TRACE("Listener constructed, Notify signal connected")
   }
   
   Listener(bool disconnect) :
   _disconnect(disconnect),
   remove(false)
   {
      notifier.Notiy().connect(this, &Listener::Notified);
      data = new int;
      traceConsructed();
   }
   
   static void traceListenerDestroy(bool disconnected) {
      if (disconnected) {
         TRACE("Listener destructed, signals disconnected")
      }
      else {
         TRACE("Listener destructed, signals not disconnected")
      }
   }
   
   ~Listener() {
      if (_disconnect) {
         disconnect();
      }
      
      delete data;
      remove = true;
      
      traceListenerDestroy(_disconnect);
   }
   
   static void traceDestructedButNotified() {
      TRACE("/!\\ Destructed listener object notified by signal, "
            "to prevent this, allways call disconnect() in listener destructors")
   }
   
   void Notified() {
      if (remove) {
         traceDestructedButNotified();
      }
      //*data = 0; // crash if remove
   }
   
   bool _disconnect;
   int *data;
   bool remove;
   Notifier notifier;
};

class SignalContainer : public Object {
public:
   AMO_SIGNAL(clicked);
   AMO_SIGNAL(dataChanged, int);
};

class SignalSlotContainer : public Object {
public:
   AMO_SIGNAL(clicked);
   AMO_SIGNAL(dataChanged, int);
};

class SlotContainer : public Object {
public:
   void clicked() {
      TRACE(this << "SlotContainer clicked");
   }
   void dataChanged(int i) {
      TRACE(this << "SlotContainer dataChanged" << i);
   }
};

int main() {
   CODE(Object1 o1);
   CODE(Object2 *o2 = new Object2);
   CODE(Object3 o3);
   CODE(Object4 *o4 = new Object4);
   CODE(Object5 o5);
   CODE(Object6 o6);
   CODE(Object7 o7(&o1));
   
   ENDL
   Console() << "o1" << &o1;
   Console() << "o2" << o2;
   Console() << "o3" << &o3;
   Console() << "o4" << o4;
   Console() << "o5" << &o5;
   Console() << "o6" << &o6;
   Console() << "o7" << &o7;
   
   ENDL
   CODE(o1.clicked().connect(o2, &Object2::click))
   CODE(o1.clicked().connect(o2, &Object2::click2))
   CODE(o1.clicked().connect(o2, &Object2::click))
   
   ENDL
   {
      TRACE_CODE(o1.emit_clicked())
      TEST_TRACE_CODE(
         o2->click();
         o2->click2();
         o2->click()
      )
   }
   
   ENDL
   CODE(o1.clicked().disconnect(o2, &Object2::click2))
   
   ENDL
   {
      TRACE_CODE(o1.emit_clicked())
      TEST_TRACE_CODE(
         o2->click();
         o2->click()
      )
   }
   
   ENDL
   CODE(o1.clicked().disconnect(o2))
   
   ENDL
   {
      TRACE_CODE(o1.emit_clicked())
      TEST_TRACE_CODE()
   }
   
   ENDL
   CODE(o1.clicked().connect(o2, &Object2::click))
   CODE(o1.clicked().connect(&o3, &Object3::click))
   CODE(o1.clicked().connect(o2, &Object2::click2))
   CODE(o1.clicked().connect(o2, &Object2::click2))
   CODE(o1.changed().connect(o2, &Object2::changed))
   CODE(o1.changed().connect(o2, &Object2::throwInt))
   CODE(o1.changed().connect(o2, &Object2::changed))
   CODE(o1.changed2().connect(o2, static_cast<void (Object2::*) (int,int,char,const char *)>(&Object2::changed)))
   
   ENDL
   {
      TRACE_CODE(o1.emit_clicked())
      TEST_TRACE_CODE(
         o2->click();
         o3.click();
         o2->click2();
         o2->click2()
      )
   }
   
   ENDL
   {
      TRACE_CODE(o1.emit_clicked2())
      TEST_TRACE_CODE()
   }
   
   ENDL
   {
      try {
         TRACE_CODE(o1.emit_changed(123))
         Console() << "TEST: FAILED";
      }
      catch (int i) {
         ::traceOutputOn = false;
         TEST_TRACE_CODE(
            o2->changed(123);
            o2->traceThrowInt(i)
         )
      }
   }
   
   ENDL
   {
      TRACE_CODE(o1.emit_changed2(123, 456, 'a', "abcd"))
      TEST_TRACE_CODE(
         o2->changed(123, 456, 'a', "abcd")
      )
   }
   
   ENDL
   CODE(o2->action().connect(o2, &Object2::click))
   CODE(o2->action().connect(&o3, &Object3::action))
   CODE(o2->charRead().connect(o2, &Object2::changed))
   
   ENDL
   {
      TRACE_CODE(o2->emit_action());
      TEST_TRACE_CODE(
         o2->click();
         o3.action()
      )
   }
   
   ENDL
   {
      TRACE_CODE(o2->emit_charRead('z'));
      TEST_TRACE_CODE(
         o2->changed('z')
      )
   }
   
   ENDL
   CODE(delete o2);
   
   ENDL
   ENDL
   {
      TRACE_CODE(o1.emit_clicked())
      TEST_TRACE_CODE(
         o3.click()
      )
   }
   
   ENDL
   CODE(o1.clicked().connect(o4, &Object4::deleteMe))
   
   ENDL
   {
      TRACE_CODE(o1.emit_clicked())
      TEST_TRACE_CODE(
         o3.click();
         o4->traceDeleted()
      )
   }
   
   ENDL
   CODE(o4 = new Object4);
   
   ENDL
   Console() << "o4" << o4;
   
   ENDL
   CODE(o5.object = o4);
   CODE(o1.clicked().connect(o4, &Object4::printMe))
   CODE(o1.clicked().connect(&o5, &Object5::deleteObject))
   CODE(o1.clicked().connect(o4, &Object4::printMe))
   CODE(o1.clicked().connect(o4, &Object4::deleteMe))
   
   ENDL
   {
      TRACE_CODE(o1.emit_clicked())
      TEST_TRACE_CODE(
         o3.click();
         o4->printMe();
         o5.traceDeleteObject(o4);
         o4->traceDeleted()
      )
   }
   
   ENDL
   CODE(o4 = new Object4);
   
   ENDL
   Console() << "o4" << o4;
   
   ENDL
   CODE(o4->remove().connect(&o3, &Object3::click))
   CODE(o4->remove().connect(o4, &Object4::deleteMe))
   CODE(o4->remove().connect(o4, &Object4::deleteMe))
   CODE(o4->remove().connect(&o3, &Object3::action))
   
   ENDL
   {
      TRACE_CODE(o4->emit_remove())
      TEST_TRACE_CODE(
         o3.click();
         o4->traceDeleted()
      )
   }
   
   ENDL
   CODE(o1.clicked().disconnect())
   CODE(o1.clicked().connect(&o3, &Object3::click))
   CODE(o1.clicked().connect(&o6, &Object6::disconnectO1Click))
   CODE(o1.clicked().connect(&o3, &Object3::action))
   CODE(o1.clicked().connect(&o3, &Object3::click))
   
   {
      TRACE_CODE(o1.emit_clicked());
      TEST_TRACE_CODE(
         o3.click();
         o6.tracedisconnectObject1clicked(&o1)
      )
   }
   
   ENDL
   CODE(o1.clicked().disconnect())
   CODE(o1.clicked().connect(&o3, &Object3::click))
   CODE(o1.clicked().connect(&o6, &Object6::connectObject1clicked))
   CODE(o1.clicked().connect(&o3, &Object3::action))
   CODE(o1.clicked().connect(&o3, &Object3::click))
   
   {
      TRACE_CODE(o1.emit_clicked());
      TEST_TRACE_CODE(
         o3.click();
         o6.traceConnectObject1clicked(&o1);
         o3.action();
         o3.click();
         o6.connected()
      )
   }
   
   ENDL
   CODE(o1.clicked().disconnect())
   CODE(o1.clicked().connect(&o3, &Object3::click))
   CODE(o1.clicked().connect(&o6, &Object6::connectObject1clicked))
   CODE(o1.clicked().connect(&o3, &Object3::action))
   CODE(o1.clicked().connect(&o3, &Object3::click))
   
   {
      TRACE_CODE(o1.emit_clicked());
      TEST_TRACE_CODE(
         o3.click();
         o6.traceConnectObject1clicked(&o1);
         o3.action();
         o3.click();
         o6.connected()
      )
   }
   
   ENDL
   CODE(o1.clicked().disconnect())
   CODE(o1.clicked().connectOnce(&o3, &Object3::click))
   CODE(o1.clicked().connectOnce(&o3, &Object3::click))
   CODE(o1.clicked().connectOnce(&o3, &Object3::click))
   CODE(o1.clicked().connect(&o3, &Object3::click))
   CODE(o1.clicked().connectOnce(&o3, &Object3::click))
   
   {
      TRACE_CODE(o1.emit_clicked());
      TEST_TRACE_CODE(
         o3.click();
         o3.click();
      )
   }
   
   ENDL
   CODE(o1.clicked().disconnect())
   CODE(o1.changed().disconnect())
   CODE(o1.clicked().connect(&o3, &Object3::click))
   CODE(o1.clicked().connect(&o7, &Object6::connectObject1clicked))
   CODE(o1.clicked().connect(&o7, &Object7::connectObject1AndEmit456))
   CODE(o1.clicked().connect(&o3, &Object3::click))
   CODE(o1.changed().connect(&o7, &Object7::changed))
   CODE(o1.changed().connect(&o3, &Object3::changed))
   {
      TRACE_CODE(o1.emit_clicked());
      TEST_TRACE_CODE(
         o3.click();
         o7.traceConnectObject1clicked(&o1);
         o7.traceConnectObject1AndEmit456();
         o1.traceEmitChanged(456);
         o7.changed(456);
         o3.changed(456);
         o3.click();
         o7.connected()
      )
   }
   
   ENDL
   {
      TRACE_CODE({ Listener(false); } )
      TEST_TRACE_CODE(
         Listener::traceConsructed();
         Listener::traceListenerDestroy(false);
         Notifier::traceNotifierDestructed();
         Listener::traceDestructedButNotified()
      )
      
      ENDL
      TRACE_CODE({ Listener(true); } )
      TEST_TRACE_CODE(
         Listener::traceConsructed();
         Listener::traceListenerDestroy(true);
         Notifier::traceNotifierDestructed();
      )
   }
   
   ENDL
   {
      CODE(SignalContainer sic)
      CODE(SignalSlotContainer sislc)
      CODE(SlotContainer slc)
      CODE(sic.clicked().connect(&sislc, &SignalSlotContainer::emit_clicked))
      CODE(sic.dataChanged().connect(&sislc, &SignalSlotContainer::emit_dataChanged))
      CODE(sislc.clicked().connect(&slc, &SlotContainer::clicked))
      CODE(sislc.dataChanged().connect(&slc, &SlotContainer::dataChanged))
      
      ENDL
      TRACE_CODE(sic.emit_clicked(); sic.emit_dataChanged(123))
      TEST_TRACE_CODE(
         slc.clicked();
         slc.dataChanged(123)
      )
   }
   
   return numFailed > 0 ? 1 : 0;
}

